package edu.mse.camel.client.uml.collab.emflistener;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;

import edu.mse.camel.client.core.events.model.ModelUpdateEvent;
import edu.mse.camel.client.core.idcache.ModelID;
import edu.mse.camel.client.core.idcache.SourceModelIDCache;
import edu.mse.camel.client.eventbus.pubsub.EventManager;
import edu.mse.camel.client.events.IEvent;
import edu.mse.camel.client.events.peer.SendGenericPeerEvent;

public abstract class CachingContentAdapter extends EContentAdapter {
	
	protected SourceModelIDCache modelIDCache;
	
	public CachingContentAdapter(EObject root) {
		modelIDCache = new SourceModelIDCache(root);
		modelIDCache.generateCache();
	}
		
	@Override
	public void notifyChanged(Notification notification) {
		super.notifyChanged(notification);
		Object notifier = notification.getNotifier();
		switch (notification.getEventType()) {
			case Notification.ADD:
				handleAdds(notification, notifier);
				break;
			case Notification.SET:
				handleSets(notification, notifier);
				break;
			case Notification.REMOVE:
				handleRemoves(notification, notifier);
				break;
		}
	}

	protected void handleRemoves(Notification notification, Object notifier) {
		if (notifier instanceof EObject) {
			ModelID modelID = modelIDCache.getID((EObject) notifier);
			if (modelID != null) {
				Object oldValueObject = notification.getOldValue();
				ModelUpdateEvent me = EventUtil.createModelEvent(notification,
						modelID, getKind(), ModelUpdateEvent.EVENT_TYPE.REMOVE);
				if (oldValueObject instanceof EObject) {
				int featureID = notification.getFeatureID(notification.getFeature().getClass());
					//cacheElement((EObject) oldValueObject);
					Resource resource =  
						new XMLResourceImpl(null);
					resource.getContents().add(EcoreUtil.copy((EObject) oldValueObject));
					try {
						ByteArrayOutputStream baos = new ByteArrayOutputStream();
						resource.save(baos, null);
						me.setByteArray(baos.toByteArray());
						me.setFeatureID(featureID);
						me.setElementModelID(modelIDCache.getID((EObject) oldValueObject));
						publishEvent(me);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	protected void handleSets(Notification notification, Object notifier) {
		if (notifier instanceof EObject) {
			ModelID modelID = modelIDCache.getID((EObject) notifier);
			if (modelID != null) {
				Object newValueObject = notification.getNewValue();
				ModelUpdateEvent me = EventUtil.createModelEvent(notification,
						modelID, getKind(), ModelUpdateEvent.EVENT_TYPE.SET);
				if (newValueObject instanceof EObject) {
					EObject newValueEObject = (EObject) newValueObject;
					ModelID newValueEObjectID = modelIDCache.getID(newValueEObject);
					if (newValueEObjectID != null) { // means element exists
						me.setElementExists(true);
						me.setExistingElementModelID(newValueEObjectID);
					} else {
						Resource resource =  
							new XMLResourceImpl(null);
						resource.getContents().add(EcoreUtil.copy(newValueEObject));
						try {
							ByteArrayOutputStream baos = new ByteArrayOutputStream();
							resource.save(baos, null);
							me.setByteArray(baos.toByteArray());
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				} else {
					me.setNewValue(newValueObject);
				}
				try {
					publishEvent(me);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	protected void handleAdds(Notification notification, Object notifier) {
		if (notifier instanceof EObject) {
			ModelID modelID = modelIDCache.getID((EObject) notifier);
			if (modelID != null) {
				Object newValueObject = notification.getNewValue();
				ModelUpdateEvent me = EventUtil.createModelEvent(notification,
						modelID, getKind(), ModelUpdateEvent.EVENT_TYPE.ADD);
				if (newValueObject instanceof EObject) {
					EObject newValueEObject = (EObject) newValueObject;
					ModelID newValueEObjectID = modelIDCache.getID(newValueEObject);
					if (newValueEObjectID != null) { // means element exists
						me.setElementExists(true);
						me.setExistingElementModelID(newValueEObjectID);
					} else {
						cacheElement(newValueEObject);
						Resource resource =  
							new XMLResourceImpl(null);
						resource.getContents().add(EcoreUtil.copy(newValueEObject));
						try {
							ByteArrayOutputStream baos = new ByteArrayOutputStream();
							resource.save(baos, null);
							me.setByteArray(baos.toByteArray());
							me.setElementModelID(modelIDCache.getID(newValueEObject));						
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
					try {
						publishEvent(me);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

	protected void publishEvent(IEvent event) {
		SendGenericPeerEvent sent = new SendGenericPeerEvent();
		sent.writeObject(event);
		EventManager.getInstance().dispatchEventAsynchronous(sent);
	}

	public SourceModelIDCache getSourceUMLModelIDCache() {
		return modelIDCache;
	}
	
	public void cacheElement(EObject newElement) {
		modelIDCache.addElementRecursive(newElement);
	}
	
	protected abstract ModelUpdateEvent.KIND getKind();
}
